﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Renci.iRods.Packets;
using System.Data;
using System.Collections;
using Renci.iRods.Common;
using System.Collections.ObjectModel;
using Renci.iRods.Data;
using System.Reflection;
using System.Globalization;
using Renci.iRods.Exceptions;
using System.Data.Common;

namespace Renci.iRods
{
    public class IRodsDataReader : DbDataReader, IEnumerator
    {
        private IRodsConnection _connection;

        private IRodsQuery _query;

        private GeneralQueryOutputPacket _outputPacket;

        private int _currentRow = -1;

        private int _totalMaxRows = 0;

        private int _totalCurrentRow = 0;

        private bool _hasRows;

        private bool _isClosed;

        private GeneralQueryInputPacket _request;

        private Dictionary<DataObjectAttributes, int> _attributeToOrdinal = new Dictionary<DataObjectAttributes, int>();

        public IRodsDataReader(IRodsConnection connection, IRodsQuery query)
        {
            this._connection = connection;

            this._query = query;

            this._totalMaxRows = query.Take;

            this._request = query.GetFirstRequest();
        }

        public override bool HasRows
        {
            get
            {
                return this._hasRows;
            }
        }

        public string GetString(DataObjectAttributes dataObjectAttribute)
        {
            return this.GetString(this._attributeToOrdinal[dataObjectAttribute]);
        }

        public long GetInt64(DataObjectAttributes dataObjectAttribute)
        {
            return this.GetInt64(this._attributeToOrdinal[dataObjectAttribute]);
        }

        public int GetInt32(DataObjectAttributes dataObjectAttribute)
        {
            return this.GetInt32(this._attributeToOrdinal[dataObjectAttribute]);
        }

        public DateTime GetDateTime(DataObjectAttributes dataObjectAttribute)
        {
            return this.GetDateTime(this._attributeToOrdinal[dataObjectAttribute]);
        }

        private void Execute()
        {
            this._connection.SendRequest(RequestCodes.GEN_QUERY_AN, this._request);

            var response = this._connection.ReceiveMessage<GeneralQueryOutputPacket>();

            this._outputPacket = response.Body;

            if (response.ErrorCode == ErrorCodes.CAT_NO_ROWS_FOUND)
            {
                //  If no rows were found create empty response
                this._outputPacket = new GeneralQueryOutputPacket();
            }
            else if (response.Exception != null)
            {
                throw response.Exception;
            }

            this._totalCurrentRow += this._outputPacket.RowsCount;

            this._hasRows = this._outputPacket.RowsCount == this._request.MaximumRows;

            if (this._totalMaxRows > 0)
            {
                this._hasRows = this._totalCurrentRow < this._totalMaxRows && this.HasRows;
            }

            if (this.HasRows)
            {
                var numOfRows = this._totalMaxRows - this._totalCurrentRow;
                if (numOfRows < 0)
                    numOfRows = 0;
                this._request = this._query.GetNextRequest(this._outputPacket.ExecutionID, numOfRows);
            }
            else
            {
                this._request = null;
            }

            //  Populate fields
            if (this._outputPacket.Results != null)
            {
                for (int i = 0; i < this._outputPacket.Results.Length; i++)
                {
                    if (this._outputPacket.Results[i].DataObjectAttribute != DataObjectAttributes.None)
                    {
                        if (!_attributeToOrdinal.ContainsKey(this._outputPacket.Results[i].DataObjectAttribute))
                            _attributeToOrdinal.Add(this._outputPacket.Results[i].DataObjectAttribute, i);
                    }
                }
            }
        }

        #region IDataReader Members

        public override void Close()
        {
            this._isClosed = true;
        }

        public override int Depth
        {
            get { return 0; }
        }

        public override DataTable GetSchemaTable()
        {
            throw new NotImplementedException();
        }

        public override bool IsClosed
        {
            get
            {
                return this._isClosed;
            }
        }

        public override bool NextResult()
        {
            return false;
        }

        public override bool Read()
        {
            if (this.IsClosed)
                return false;

            if (this._outputPacket == null)
            {
                this.Execute();
            }

            this._currentRow++;

            var result = this._currentRow < this._outputPacket.RowsCount;

            if (!result && this.HasRows)
            {
                this.Execute();

                this._currentRow = 0;
                result = this._outputPacket.Results != null;
            }

            return result;
        }

        public override int RecordsAffected
        {
            get { return -1; }
        }

        #endregion

        #region IDataRecord Members

        public override int FieldCount
        {
            get
            {
                return this._attributeToOrdinal.Keys.Count;
            }
        }

        public override bool GetBoolean(int i)
        {
            throw new NotImplementedException();
        }

        public override byte GetByte(int i)
        {
            throw new NotImplementedException();
        }

        public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            throw new NotImplementedException();
        }

        public override char GetChar(int i)
        {
            throw new NotImplementedException();
        }

        public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            throw new NotImplementedException();
        }

        public override string GetDataTypeName(int i)
        {
            throw new NotImplementedException();
        }

        public override DateTime GetDateTime(int i)
        {
            try
            {
                var value = this._outputPacket.Results[i].Values[this._currentRow];

                if (string.IsNullOrEmpty(value))
                    return default(DateTime);
                else
                {
                    return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(double.Parse(value)));
                }
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override decimal GetDecimal(int i)
        {
            try
            {
                var value = this._outputPacket.Results[i].Values[this._currentRow];

                if (string.IsNullOrEmpty(value))
                    return default(decimal);
                else
                    return decimal.Parse(value);
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override double GetDouble(int i)
        {
            try
            {
                var value = this._outputPacket.Results[i].Values[this._currentRow];

                if (string.IsNullOrEmpty(value))
                    return default(double);
                else
                    return double.Parse(value);
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override Type GetFieldType(int i)
        {
            throw new NotImplementedException();
        }

        public override float GetFloat(int i)
        {
            try
            {
                var value = this._outputPacket.Results[i].Values[this._currentRow];

                if (string.IsNullOrEmpty(value))
                    return default(float);
                else
                    return float.Parse(value);
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override Guid GetGuid(int i)
        {
            throw new NotImplementedException();
        }

        public override short GetInt16(int i)
        {
            try
            {
                var value = this._outputPacket.Results[i].Values[this._currentRow];

                if (string.IsNullOrEmpty(value))
                    return default(short);
                else
                    return short.Parse(value);
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override int GetInt32(int i)
        {
            try
            {
                var value = this._outputPacket.Results[i].Values[this._currentRow];

                if (string.IsNullOrEmpty(value))
                    return default(int);
                else
                    return int.Parse(value);
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override long GetInt64(int i)
        {
            try
            {
                var value = this._outputPacket.Results[i].Values[this._currentRow];

                if (string.IsNullOrEmpty(value))
                    return default(long);
                else
                    return long.Parse(value);
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override string GetName(int i)
        {
            throw new NotImplementedException();
        }

        public override int GetOrdinal(string name)
        {
            throw new NotImplementedException();
        }

        public override string GetString(int i)
        {
            try
            {
                return this._outputPacket.Results[i].Values[this._currentRow];
            }
            catch (InvalidOperationException exp)
            {
                throw new IRodsException(string.Format("'{0}' is invalid field number.", i));
            }
        }

        public override object GetValue(int i)
        {
            return this.GetString(i);
        }

        public override int GetValues(object[] values)
        {
            throw new NotImplementedException();
        }

        public override bool IsDBNull(int i)
        {
            throw new NotImplementedException();
        }

        public override object this[string name]
        {
            get { return this.GetString((DataObjectAttributes)Enum.Parse(typeof(DataObjectAttributes), name)); }
        }

        public override object this[int i]
        {
            get { return this.GetString(i); }
        }

        #endregion

        #region IEnumerable Members

        public override IEnumerator GetEnumerator()
        {
            return (IEnumerator)this;
        }

        #endregion

        #region IEnumerator Members

        public object Current
        {
            get { return this; }
        }

        public bool MoveNext()
        {
            return this.Read();
        }

        public void Reset()
        {
            this._currentRow = -1;
            this._totalCurrentRow = -1;
        }

        #endregion

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                try
                {
                    this.Close();
                }
                catch (Exception e)
                {
                    throw new SystemException(string.Format("An exception of type {0} was encountered while closing the TemplateDataReader.", e.GetType()));
                }
            }
        }

    }
}
